package sharpen.core.framework;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FileASTRequestor;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public abstract class ConversionBatch {

    private String[] _sourceFiles;
    private String[] _sourcePathEntries;
    private String[] _classPathEntries;

    private IProgressMonitor _progressMonitor = new NullProgressMonitor();

    private final ASTParser _parser;
    private boolean _continueOnError;
    private boolean _copySharpenCs;

    public ConversionBatch() {
        _parser = ASTParser.newParser(AST.JLS8);
        _parser.setKind(ASTParser.K_COMPILATION_UNIT);

        @SuppressWarnings("unchecked")
        Map<String, String> options = JavaCore.getOptions();
        options.put(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_1_8);
        options.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JavaCore.VERSION_1_8);
        options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_8);
        _parser.setCompilerOptions(options);
    }

    public boolean isContinueOnError() {
        return _continueOnError;
    }

    public void setContinueOnError(boolean continueOnError) {
        this._continueOnError = continueOnError;
    }

    public void setCopySharpenCs(boolean copySharpenCs) {
        this._copySharpenCs = copySharpenCs;
    }

    /**
     * Defines the set of java source files to be converted.
     *
     * @param source iterator of sourceFiles instances
     */
    public void setSourceFiles(String... sourceFiles) {
        if (null == sourceFiles || 0 == sourceFiles.length) {
            throw new IllegalArgumentException("sourceFiles");
        }
        _sourceFiles = sourceFiles;
    }

    public void setSourceFiles(List<String> sourceFiles) {
        if (null == sourceFiles || sourceFiles.isEmpty()) {
            throw new IllegalArgumentException("sourceFiles");
        }
        _sourceFiles = sourceFiles.toArray(new String[sourceFiles.size()]);
    }

    /**
     * Defines the set of java source files path to be converted.
     *
     * @param source iterator of sourcePathEntries instances
     */
    public void setSourcePathEntries(String... sourcePathEntries) {
        if (null == sourcePathEntries || 0 == sourcePathEntries.length) {
            throw new IllegalArgumentException("sourcePathEntries");
        }
        _sourcePathEntries = sourcePathEntries;
    }

    public void setSourcePathEntries(List<String> sourcePathEntries) {
        if (null == sourcePathEntries || sourcePathEntries.isEmpty()) {
            throw new IllegalArgumentException("sourcePathEntries");
        }
        _sourcePathEntries = sourcePathEntries.toArray(new String[sourcePathEntries.size()]);
    }

    /**
     * Defines the set of java executable files path to be converted.
     *
     * @param source iterator of classPathEntries instances
     */
    public void setClassPathEntries(String... classPathEntries) {
        if (null == classPathEntries || 0 == classPathEntries.length) {
            throw new IllegalArgumentException("classPathEntries");
        }
        _classPathEntries = classPathEntries;
    }

    public void setClassPathEntries(List<String> classPathEntries) {
        if (null != classPathEntries || !classPathEntries.isEmpty()) {
            _classPathEntries = classPathEntries.toArray(new String[classPathEntries.size()]);
        }
    }

    public void setProgressMonitor(IProgressMonitor monitor) {
        if (null == monitor) {
            throw new IllegalArgumentException("monitor");
        }
        _progressMonitor = monitor;
    }

    /**
     * @throws CoreException
     * @throws IOException
     * @throws InterruptedException
     * @throws IllegalStateException when source is not set
     */
    public void run() throws CoreException, IOException {

        if (null == _sourceFiles) {
            throw new IllegalStateException("source was not set");
        }

        final ArrayList<CompilationUnitPair> pairs = parseCompilationUnits();

        PreConvertVisitor preConvertVisitor = new PreConvertVisitor();
        for (final CompilationUnitPair pair : pairs) {
            pair.ast.accept(preConvertVisitor);
        }
        final ASTResolver resolver = new DefaultASTResolver(pairs, preConvertVisitor.getRenamingMap());

        _progressMonitor.beginTask("converting", pairs.size());
        for (final CompilationUnitPair pair : pairs) {
            if (_progressMonitor.isCanceled()) return;

            try {
                convertPair(resolver, pair);
            } catch (RuntimeException ex) {
                if (!isContinueOnError()) {
                    throw ex;
                }

                if (ex instanceof IllegalArgumentException || ex instanceof ClassCastException) {
                    // we still want to notify the user about the problem
                    ex.printStackTrace(System.err);
                } else {
                    // not a recoverable exception
                    throw ex;
                }
            }
        }
        if (_copySharpenCs && !_progressMonitor.isCanceled()) {
            copySharpenCs();
        }
    }


    private void convertPair(final ASTResolver resolver, final CompilationUnitPair pair) throws CoreException, IOException {
        try {
            _progressMonitor.subTask(pair.source.replace("\\", "/"));
            convertCompilationUnit(resolver, pair.source.replace("\\", "/"), pair.ast);
        } finally {
            _progressMonitor.worked(1);
        }
    }

    protected abstract void convertCompilationUnit(ASTResolver resolver, String sourceFiles, CompilationUnit ast) throws CoreException, IOException;

    protected abstract void copySharpenCs() throws IOException;

    private ArrayList<CompilationUnitPair> parseCompilationUnits() {
        final ArrayList<CompilationUnitPair> pairs = new ArrayList<CompilationUnitPair>(_sourceFiles.length);
        FileASTRequestor requestor = new FileASTRequestor() {
            @Override
            public void acceptAST(String source, CompilationUnit ast) {
                pairs.add(new CompilationUnitPair(source, ast));
            }
        };
        _parser.setEnvironment(_classPathEntries, _sourcePathEntries, null, true);
        _parser.setResolveBindings(true);
        _parser.createASTs(_sourceFiles, null, new String[0], requestor, _progressMonitor);
        return pairs;
    }

}